/*
 * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *    http://aws.amazon.com/apache2.0
 *
 * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and
 * limitations under the License.
 */

package com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper;

import static org.easymock.EasyMock.anyObject;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.HashKeyAutoGenerated;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapper.FailedBatch;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapper.SaveObjectHandler;
import com.amazonaws.mobileconnectors.dynamodbv2.dynamodbmapper.DynamoDBMapperConfig.PaginationLoadingStrategy;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
import com.amazonaws.services.dynamodbv2.model.BatchGetItemRequest;
import com.amazonaws.services.dynamodbv2.model.BatchGetItemResult;
import com.amazonaws.services.dynamodbv2.model.BatchWriteItemRequest;
import com.amazonaws.services.dynamodbv2.model.BatchWriteItemResult;
import com.amazonaws.services.dynamodbv2.model.Condition;
import com.amazonaws.services.dynamodbv2.model.ExpectedAttributeValue;
import com.amazonaws.services.dynamodbv2.model.KeysAndAttributes;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.WriteRequest;
import com.amazonaws.util.StringUtils;

import org.easymock.Capture;
import org.easymock.CaptureType;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DynamoDBMapperTest {

    private AmazonDynamoDB mockClient;
    private DynamoDBMapper mapper;
    private final PaginationLoadingStrategy strategy = PaginationLoadingStrategy.LAZY_LOADING;
    private DynamoDBMapperConfig config;

    @Before
    public void setup() {
        config = new DynamoDBMapperConfig(strategy);
        mockClient = EasyMock.createMock(AmazonDynamoDBClient.class);
        mapper = new DynamoDBMapper(mockClient);
    }

    @Test
    public void testCreateKeyObjectTest() {
        IndexRangeKeyClass keyClass = mapper.createKeyObject(IndexRangeKeyClass.class,
                Long.parseLong("5"), Double.parseDouble("9"));
        assertEquals(keyClass.getKey(), 5L);
        assertEquals(keyClass.getRangeKey(), 9.0, .005);
    }

    @Test(expected = DynamoDBMappingException.class)
    public void testCreateKeyObjectTestNoHashKeyAnnotation() {
        TestClass keyClass = mapper.createKeyObject(TestClass.class, Long.parseLong("5"),
                Double.parseDouble("9"));
    }

    @Test(expected = DynamoDBMappingException.class)
    public void testCreateKeyObjectTestNoRangeKeyAnnotation() {
        MockTwoValuePlusVersionClass keyClass = mapper.createKeyObject(
                MockTwoValuePlusVersionClass.class, "Hash", "Range");
    }

    @Test
    public void testTransformAttributeUpdates() {

        mockClient = EasyMock.createMock(AmazonDynamoDBClient.class);
        mapper = new DynamoDBMapper(mockClient, config, new AttributeTransformer() {

            @Override
            public Map<String, AttributeValue> transform(Parameters<?> parameters) {
                Map<String, AttributeValue> upperCased = new HashMap<String, AttributeValue>();
                for (Map.Entry<String, AttributeValue> curr : parameters.getAttributeValues()
                        .entrySet()) {
                    upperCased.put(curr.getKey(),
                            new AttributeValue().withS(StringUtils.upperCase(curr.getValue().getS())));
                }
                return upperCased;
            }

            @Override
            public Map<String, AttributeValue> untransform(Parameters<?> parameters) {
                // TODO Auto-generated method stub
                return null;
            }

        });

        Map<String, AttributeValue> keys = new HashMap<String, AttributeValue>();
        keys.put("id", new AttributeValue().withS("hashKey"));
        Map<String, AttributeValueUpdate> updates = new HashMap<String, AttributeValueUpdate>();
        AttributeValueUpdate update = new AttributeValueUpdate().withValue(new AttributeValue()
                .withS("newValue1"));
        updates.put("firstValue", update);

        Map<String, AttributeValueUpdate> transformed = mapper.transformAttributeUpdates(
                MockTwoValuePlusVersionClass.class, "aws-android-sdk-dynamodbmapper-test", keys,
                updates, config);
        assertEquals(transformed.get("firstValue").getValue().getS(), "NEWVALUE1");
        assertNull(transformed.get("id"));
    }

    @Test
    public void testWriteOneBatchWithEntityTooLarge() {
        Map<String, List<WriteRequest>> batchMap = new HashMap<String, List<WriteRequest>>();
        List<WriteRequest> batchList = new ArrayList<WriteRequest>();
        WriteRequest wr1 = new WriteRequest();
        WriteRequest wr2 = new WriteRequest();
        WriteRequest wr3 = new WriteRequest();
        batchList.add(wr1);
        batchList.add(wr2);
        batchList.add(wr3);
        batchMap.put("testTable", batchList);
        EasyMock.reset(mockClient);

        AmazonServiceException ase = new AmazonServiceException("TestException");
        ase.setErrorCode("Request entity too large");

        BatchWriteItemResult mockResult = EasyMock.createMock(BatchWriteItemResult.class);
        EasyMock.reset(mockResult);
        EasyMock.expect(mockResult.getUnprocessedItems()).andReturn(
                new HashMap<String, List<WriteRequest>>()).times(2);
        // Will cause batches to be split and re-tried
        EasyMock.expect(mockClient.batchWriteItem(anyObject(BatchWriteItemRequest.class)))
                .andThrow(ase);
        EasyMock.expect(mockClient.batchWriteItem(anyObject(BatchWriteItemRequest.class)))
                .andReturn(mockResult);
        EasyMock.expect(mockClient.batchWriteItem(anyObject(BatchWriteItemRequest.class)))
                .andReturn(mockResult);
        EasyMock.replay(mockClient, mockResult);

        List<FailedBatch> result = mapper.writeOneBatch(batchMap);
        assertEquals(result.size(), 0);
        EasyMock.verify(mockClient);
    }

    @Test
    public void testBatchLoadRetiresForUnprocessedItems() {
        List<Object> itemsToGet = new ArrayList<Object>();
        itemsToGet.add(new MockTwoValuePlusVersionClass("PrimaryKey",
                "Value1", null));
        itemsToGet.add(new MockDifferentTableName("OtherPrimaryKey", "OtherValue1"));

        EasyMock.reset(mockClient);

        // First result will show that the first item was processed
        // successfully, and the second item needs to be retried
        BatchGetItemResult firstResult = new BatchGetItemResult();
        Map<String, List<Map<String, AttributeValue>>> responses = new HashMap<String, List<Map<String, AttributeValue>>>();

        List<Map<String, AttributeValue>> items = new ArrayList<Map<String, AttributeValue>>();

        Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
        item.put("id", new AttributeValue().withS("idValue"));
        item.put("firstValue", new AttributeValue().withS("firstValueValue"));
        item.put("secondValue", new AttributeValue().withS("secondValueValue"));
        item.put("version", new AttributeValue().withN("1"));

        items.add(item);
        responses.put(mapper.getTableName(MockTwoValuePlusVersionClass.class, config), items);

        firstResult.withResponses(responses);
        // Do Not process second table on first go around
        Map<String, KeysAndAttributes> unprocessedObjects = new HashMap<String, KeysAndAttributes>();
        KeysAndAttributes unprocessedObject = new KeysAndAttributes();
        Map<String, AttributeValue> unprocessedKey = new HashMap<String, AttributeValue>();
        unprocessedKey.put("id", new
                AttributeValue().withS("OtherPrimaryKey"));
        unprocessedObject.withKeys(unprocessedKey);
        unprocessedObjects.put("MockDifferentTableName", unprocessedObject);
        firstResult.withUnprocessedKeys(unprocessedObjects);

        // EasyMock is broken and will change all captured values to the last
        // capture, even if the capture
        // objects are different and set to CaptureType.ALL this is used so that
        // we can verify arguments
        FixedCapture<BatchGetItemRequest> capture = new FixedCapture<BatchGetItemRequest>(
                CaptureType.ALL, new FixedCapture.CapCallback<BatchGetItemRequest>() {

                    @Override
                    public void valueSet(BatchGetItemRequest value) {
                        assertEquals(value.getRequestItems().size(), 2);
                    }

                });
        EasyMock.expect(mockClient.batchGetItem(EasyMock.capture(capture))).andReturn(
                firstResult);

        BatchGetItemResult secondResult = new BatchGetItemResult();
        Map<String, List<Map<String, AttributeValue>>> secondResponses = new HashMap<String, List<Map<String, AttributeValue>>>();

        List<Map<String, AttributeValue>> secondItems = new ArrayList<Map<String, AttributeValue>>();

        Map<String, AttributeValue> secondItem = new HashMap<String, AttributeValue>();
        secondItem.put("id", new AttributeValue().withS("idValue2"));
        secondItem.put("firstValue", new AttributeValue().withS("firstValueValue2"));

        secondItems.add(secondItem);
        responses.put(mapper.getTableName(MockDifferentTableName.class, config), secondItems);
        secondResult.withResponses(secondResponses);
        FixedCapture<BatchGetItemRequest> capture2 = new FixedCapture<BatchGetItemRequest>(
                CaptureType.ALL, new FixedCapture.CapCallback<BatchGetItemRequest>() {

                    @Override
                    public void valueSet(BatchGetItemRequest value) {
                        assertEquals(value.getRequestItems().size(), 1);
                    }

                });

        EasyMock.expect(mockClient.batchGetItem(EasyMock.capture(capture2))).andReturn(
                secondResult);

        EasyMock.replay(mockClient);

        Map<String, List<Object>> loadResults = mapper.batchLoad(itemsToGet);

        EasyMock.verify(mockClient);
        assertEquals(loadResults.keySet().size(), 2);
        assertEquals(loadResults.get("aws-android-sdk-dynamodbmapper-test").size(), 1);
        assertEquals(loadResults.get("aws-android-sdk-dynamodbmapper-test-different-table").size(),
                1);
        assertEquals(loadResults.get("aws-android-sdk-dynamodbmapper-test").get(0).getClass(),
                MockTwoValuePlusVersionClass.class);
        assertEquals(loadResults.get("aws-android-sdk-dynamodbmapper-test-different-table").get(0)
                .getClass(), MockDifferentTableName.class);

    }

    @Test
    public void testMergeExpectedAttributeValueConditions() {
        Map<String, ExpectedAttributeValue> internalAssertions = new HashMap<String, ExpectedAttributeValue>();
        Map<String, ExpectedAttributeValue> userProvidedConditions = new HashMap<String, ExpectedAttributeValue>();

        ExpectedAttributeValue internal = new ExpectedAttributeValue()
                .withValue(new AttributeValue().withS("internal"));
        ExpectedAttributeValue user = new ExpectedAttributeValue().withValue(new AttributeValue()
                .withS("user"));
        ExpectedAttributeValue bothInterlan = new ExpectedAttributeValue()
                .withValue(new AttributeValue().withS("bothInterlan"));
        ExpectedAttributeValue bothUser = new ExpectedAttributeValue()
                .withValue(new AttributeValue().withS("bothUser"));

        internalAssertions.put("internal", internal);
        userProvidedConditions.put("user", user);
        internalAssertions.put("both", bothInterlan);
        userProvidedConditions.put("both", bothUser);

        Map<String, ExpectedAttributeValue> merged = DynamoDBMapper
                .mergeExpectedAttributeValueConditions(
                        internalAssertions, userProvidedConditions, "AND");
        assertEquals(merged.get("internal").getValue().getS(), "internal");
        assertEquals(merged.get("user").getValue().getS(), "user");
        assertEquals(merged.get("both").getValue().getS(), "bothUser");
    }

    @Test(expected = IllegalArgumentException.class)
    public void testMergeExpectedAttributeValueConditionsInvalidOperator() {
        Map<String, ExpectedAttributeValue> internalAssertions = new HashMap<String, ExpectedAttributeValue>();
        Map<String, ExpectedAttributeValue> userProvidedConditions = new HashMap<String, ExpectedAttributeValue>();

        ExpectedAttributeValue internal = new ExpectedAttributeValue()
                .withValue(new AttributeValue().withS("internal"));
        ExpectedAttributeValue user = new ExpectedAttributeValue().withValue(new AttributeValue()
                .withS("user"));
        ExpectedAttributeValue bothInterlan = new ExpectedAttributeValue()
                .withValue(new AttributeValue().withS("bothInterlan"));
        ExpectedAttributeValue bothUser = new ExpectedAttributeValue()
                .withValue(new AttributeValue().withS("bothUser"));

        internalAssertions.put("internal", internal);
        userProvidedConditions.put("user", user);
        internalAssertions.put("both", bothInterlan);
        userProvidedConditions.put("both", bothUser);

        Map<String, ExpectedAttributeValue> merged = DynamoDBMapper
                .mergeExpectedAttributeValueConditions(
                        internalAssertions, userProvidedConditions, "OR");
    }

    @Test
    public void testMergeExpectedAttributeValueConditionsNoInternalAssertions() {
        Map<String, ExpectedAttributeValue> userProvidedConditions = new HashMap<String, ExpectedAttributeValue>();

        ExpectedAttributeValue user = new ExpectedAttributeValue().withValue(new AttributeValue()
                .withS("user"));

        userProvidedConditions.put("user", user);

        Map<String, ExpectedAttributeValue> merged = DynamoDBMapper
                .mergeExpectedAttributeValueConditions(
                        null, userProvidedConditions, "AND");
        assertEquals(merged.get("user").getValue().getS(), "user");
        assertEquals(merged.size(), 1);
    }

    @Test
    public void testMergeExpectedAttributeValueConditionsNoUserProvidedConditions() {
        Map<String, ExpectedAttributeValue> internalAssertions = new HashMap<String, ExpectedAttributeValue>();

        ExpectedAttributeValue internal = new ExpectedAttributeValue()
                .withValue(new AttributeValue().withS("internal"));

        internalAssertions.put("internal", internal);

        Map<String, ExpectedAttributeValue> merged = DynamoDBMapper
                .mergeExpectedAttributeValueConditions(
                        internalAssertions, null, "AND");
        assertEquals(merged.get("internal").getValue().getS(), "internal");
        assertEquals(merged.size(), 1);
    }

    @Test
    public void testNeedAutoGenerateAssignableKey() {
        HashKeyAutoGenerated autoGenObj = new HashKeyAutoGenerated();
        assertTrue(mapper.needAutoGenerateAssignableKey(HashKeyAutoGenerated.class, autoGenObj));
        MockTwoValuePlusVersionClass nonAutogen = new MockTwoValuePlusVersionClass();
        assertFalse(mapper.needAutoGenerateAssignableKey(MockTwoValuePlusVersionClass.class,
                nonAutogen));
    }

    @Test
    public void testBatchLoadReturnsEmptyMapWithRequestOfNoObjects() {
        Map<String, List<Object>> result = mapper.batchLoad(new ArrayList<Object>());
        assertEquals(result.keySet().size(), 0);
    }

    @Test
    public void testCreateScanRequestFromExpression() {

        DynamoDBScanExpression se = new DynamoDBScanExpression();
        se.setConditionalOperator("lt");
        Map<String, AttributeValue> esk = new HashMap<String, AttributeValue>();
        se.setExclusiveStartKey(esk);
        Map<String, String> ean = new HashMap<String, String>();
        se.setExpressionAttributeNames(ean);
        Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
        se.setExpressionAttributeValues(eav);
        se.setFilterExpression("testFilter");
        se.setLimit(5);
        Map<String, Condition> filter = new HashMap<String, Condition>();
        se.setScanFilter(filter);
        se.setSegment(2);
        se.setTotalSegments(10);

        ScanRequest sr = mapper.createScanRequestFromExpression(MockTwoValuePlusVersionClass.class,
                se, config);

        assertEquals(sr.getConditionalOperator(), "lt");
        assertEquals(sr.getTableName(), "aws-android-sdk-dynamodbmapper-test");
        assertEquals(sr.getExclusiveStartKey(), esk);
        assertEquals(sr.getExpressionAttributeNames(), ean);
        assertEquals(sr.getExpressionAttributeValues(), eav);
        assertEquals(sr.getFilterExpression(), "testFilter");
        assertEquals(sr.getLimit().intValue(), 5);
        assertEquals(sr.getScanFilter(), filter);
        assertEquals(sr.getSegment().intValue(), 2);
        assertEquals(sr.getTotalSegments().intValue(), 10);
    }

    @Test
    public void createParalellScanRequestsFromExpression() {
        DynamoDBScanExpression se = new DynamoDBScanExpression();
        se.setConditionalOperator("lt");
        Map<String, AttributeValue> esk = new HashMap<String, AttributeValue>();
        se.setExclusiveStartKey(esk);
        Map<String, String> ean = new HashMap<String, String>();
        se.setExpressionAttributeNames(ean);
        Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
        se.setExpressionAttributeValues(eav);
        se.setFilterExpression("testFilter");
        se.setLimit(5);
        Map<String, Condition> filter = new HashMap<String, Condition>();
        se.setScanFilter(filter);
        se.setSegment(2);
        se.setTotalSegments(10);

        List<ScanRequest> requests = mapper.createParallelScanRequestsFromExpression(
                MockTwoValuePlusVersionClass.class,
                se, 2, config);

        ScanRequest sr1 = requests.get(0);

        assertEquals(sr1.getConditionalOperator(), "lt");
        assertEquals(sr1.getTableName(), "aws-android-sdk-dynamodbmapper-test");
        assertNull(sr1.getExclusiveStartKey());
        assertEquals(sr1.getExpressionAttributeNames(), ean);
        assertEquals(sr1.getExpressionAttributeValues(), eav);
        assertEquals(sr1.getFilterExpression(), "testFilter");
        assertEquals(sr1.getLimit().intValue(), 5);
        assertEquals(sr1.getScanFilter(), filter);
        assertEquals(sr1.getSegment().intValue(), 0);
        assertEquals(sr1.getTotalSegments().intValue(), 2);

        ScanRequest sr2 = requests.get(1);

        assertEquals(sr2.getConditionalOperator(), "lt");
        assertEquals(sr2.getTableName(), "aws-android-sdk-dynamodbmapper-test");
        assertNull(sr2.getExclusiveStartKey());
        assertEquals(sr2.getExpressionAttributeNames(), ean);
        assertEquals(sr2.getExpressionAttributeValues(), eav);
        assertEquals(sr2.getFilterExpression(), "testFilter");
        assertEquals(sr2.getLimit().intValue(), 5);
        assertEquals(sr2.getScanFilter(), filter);
        assertEquals(sr2.getSegment().intValue(), 1);
        assertEquals(sr2.getTotalSegments().intValue(), 2);
    }

    @Test
    public void testContainsThrottlingException() {
        List<FailedBatch> failedBatches = new ArrayList<FailedBatch>();

        FailedBatch nonThrottle = new FailedBatch();
        nonThrottle.setException(new AmazonServiceException("InvalidInput"));
        failedBatches.add(nonThrottle);

        assertFalse(mapper.containsThrottlingException(failedBatches));

        FailedBatch throttle = new FailedBatch();
        AmazonServiceException ase = new AmazonServiceException("ThrottlingException");
        ase.setErrorCode("ThrottlingException");
        nonThrottle.setException(ase);
        failedBatches.add(throttle);

        assertTrue(mapper.containsThrottlingException(failedBatches));
    }

    @Test
    public void testSaveObjectHandler() {

        MockTwoValuePlusVersionClass mockClass = new MockTwoValuePlusVersionClass("PrimaryKey",
                "Value1", null);
        String tableName = mapper.getTableName(MockTwoValuePlusVersionClass.class, mockClass,
                config);

        final Map<String, Boolean> expectedFound = new HashMap<String, Boolean>();
        final String foundNullAttributeKey = "NullAttribute";
        final String foundKeyKey = "Key";

        SaveObjectHandler testHandler = mapper.new SaveObjectHandler(
                MockTwoValuePlusVersionClass.class, mockClass,
                tableName, config, mapper.getConverter(config), null) {

            @Override
            protected void onKeyAttributeValue(String attributeName,
                    AttributeValue keyAttributeValue) {
                if (attributeName.equalsIgnoreCase("id")
                        && keyAttributeValue.getS().equalsIgnoreCase("PrimaryKey")) {
                    expectedFound.put(foundKeyKey, true);
                } else {
                    fail("Incorrect onKeyAttributeValueCalled --- received attributeName: "
                            + attributeName + " with value: " + keyAttributeValue.getS());
                }
            }

            @Override
            protected void onNullNonKeyAttribute(String attributeName) {
                if (attributeName.equalsIgnoreCase("SecondValue")) {
                    expectedFound.put(foundNullAttributeKey, true);
                } else {
                    fail("Incorrect NullNonKeyAttribute called, received " + attributeName);
                }
            }

            @Override
            protected void executeLowLevelRequest() {
                assertTrue(expectedFound.get(foundKeyKey));
                assertTrue(expectedFound.get(foundNullAttributeKey));
                assertTrue(getAttributeValueUpdates().get("firstValue").getValue().getS()
                        .equalsIgnoreCase("Value1"));
                assertTrue(getAttributeValueUpdates().get("version").getValue().getN()
                        .equalsIgnoreCase("1"));
            }

        };

        testHandler.execute();

    }

    @Test
    public void testSaveObjectHandlerWithAutogeneratedKey() {

        HashKeyAutoGenerated mockClass = new HashKeyAutoGenerated();
        mockClass.setRangeKey("range");
        mockClass.setOtherAttribute("other");
        String tableName = mapper.getTableName(HashKeyAutoGenerated.class, mockClass,
                config);

        final Map<String, Boolean> expectedFound = new HashMap<String, Boolean>();
        final String foundRangeKey = "RangeKey";

        SaveObjectHandler testHandler = mapper.new SaveObjectHandler(
                HashKeyAutoGenerated.class, mockClass,
                tableName, config, mapper.getConverter(config), null) {

            @Override
            protected void onKeyAttributeValue(String attributeName,
                    AttributeValue keyAttributeValue) {
                if (attributeName.equalsIgnoreCase("rangeKey")
                        && keyAttributeValue.getS().equalsIgnoreCase("range")) {
                    expectedFound.put(foundRangeKey, true);
                } else {
                    fail("Incorrect onKeyAttributeValueCalled --- received attributeName: "
                            + attributeName + " with value: " + keyAttributeValue.getS());
                }
            }

            @Override
            protected void onNullNonKeyAttribute(String attributeName) {
                fail("No null attributes should have been found");
            }

            @Override
            protected void executeLowLevelRequest() {
                assertNotNull(getAttributeValueUpdates().get("key").getValue().getS());
                assertTrue(expectedFound.get(foundRangeKey));
                assertTrue(getAttributeValueUpdates().get("otherAttribute").getValue().getS()
                        .equalsIgnoreCase("other"));
            }

        };

        testHandler.execute();

    }

    // ----Mock test classes -----

    @DynamoDBTable(tableName = "aws-android-sdk-dynamodbmapper-test")
    private static final class MockTwoValuePlusVersionClass {
        private String id;
        private String firstValue;
        private String secondValue;
        private Integer version;

        public MockTwoValuePlusVersionClass() {
        }

        public MockTwoValuePlusVersionClass(String id, String firstValue, String secondValue) {
            this.id = id;
            this.firstValue = firstValue;
            this.secondValue = secondValue;
        }

        @DynamoDBHashKey
        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        @DynamoDBVersionAttribute
        public Integer getVersion() {
            return version;
        }

        public void setVersion(Integer version) {
            this.version = version;
        }

        @DynamoDBAttribute
        public String getFirstValue() {
            return firstValue;
        }

        public void setFirstValue(String value) {
            this.firstValue = value;
        }

        public String getSecondValue() {
            return secondValue;
        }

        public void setSecondValue(String secondValue) {
            this.secondValue = secondValue;
        }
    }

    @DynamoDBTable(tableName = "aws-android-sdk-dynamodbmapper-test-different-table")
    private static final class MockDifferentTableName {

        private String id;
        private String firstValue;

        public MockDifferentTableName(String id, String firstValue) {
            this.id = id;
            this.firstValue = firstValue;
        }

        public MockDifferentTableName() {

        }

        @DynamoDBHashKey
        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        @DynamoDBAttribute
        public String getFirstValue() {
            return firstValue;
        }

        public void setFirstValue(String firstValue) {
            this.firstValue = firstValue;
        }
    }

    private static final class FixedCapture<T> extends Capture<T> {

        public static interface CapCallback<T> {
            public void valueSet(T value);
        }

        CapCallback<T> callback;

        public FixedCapture(CaptureType all, CapCallback callback) {
            super(CaptureType.ALL);
            this.callback = callback;
        }

        @Override
        public void setValue(T value) {
            callback.valueSet(value);
            if (!hasCaptured()) {
                super.setValue(value);
            }
        }
    }

}
